package ru.myrtle.jst;

import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import ru.myrtle.jst.ast.Unit;
import ru.myrtle.jst.emitter.Emitter;
import ru.myrtle.jst.i18n.NLS;
import ru.myrtle.jst.parser.Parser;
import ru.myrtle.jst.store.Instance;
import ru.myrtle.jst.store.InstanceStore;
import ru.myrtle.jst.store.Template;
import ru.myrtle.jst.store.TemplateStore;

public class Project {

    private TemplateStore templateStore;

    public void setTemplateStore(TemplateStore templateStore) {
        this.templateStore = templateStore;
    }

    /*
     *
     */

    private Syntax syntax;

    public void setSyntax(Syntax syntax) {
        this.syntax = syntax;
    }

    /*
     *
     */

    private Emitter emitter;

    public void setEmitter(Emitter emitter) {
        this.emitter = emitter;
    }

    /*
     *
     */

    private InstanceStore instanceStore;

    public void setInstanceStore(InstanceStore instanceStore) {
        this.instanceStore = instanceStore;
    }

    /*
     *
     */

    private final List<Problem> problems =
            new ArrayList<Problem>();

    public List<Problem> getProblems() {
        return getProblems(
                EnumSet.allOf(Problem.Severity.class)
        );
    }

    public List<Problem> getProblems(EnumSet<Problem.Severity> severities) {
        List<Problem> list = new ArrayList<Problem>();

        for (Problem problem : problems) {
            if (severities.contains(problem.getSeverity())) {
                list.add(problem);
            }
        }

        return list;
    }

    public boolean hasProblems() {
        return hasProblems(
                EnumSet.allOf(Problem.Severity.class)
        );
    }

    public boolean hasProblems(EnumSet<Problem.Severity> severities) {
        for (Problem problem : problems) {
            if (severities.contains(problem.getSeverity())) {
                return true;
            }
        }

        return false;
    }

    public void addProblems(Collection<Problem> list) {
        problems.addAll(list);
    }

    public void addProblems(Problem... list) {
        problems.addAll(
                Arrays.asList(list)
        );
    }

    /*
     *
     */

    private final Map<String, Unit> units =
            new HashMap<String, Unit>();

    public Unit getUnit(String name) {
        return units.get(name);
    }

    /*
     *
     */

    void validate() {
        if (templateStore == null) {
            throw new IllegalArgumentException(
                    NLS.BUNDLE.getString(NLS.KEY_COMPILER_TEMPLATE_STORE_NOT_SPECIFIED)
            );
        }
        if (syntax == null) {
            throw new IllegalArgumentException(
                    NLS.BUNDLE.getString(NLS.KEY_COMPILER_SYNTAX_NOT_SPECIFIED)
            );
        }
        if (emitter == null) {
            throw new IllegalArgumentException(
                    NLS.BUNDLE.getString(NLS.KEY_COMPILER_EMITTER_NOT_SPECIFIED)
            );
        }
        if (instanceStore == null) {
            throw new IllegalArgumentException(
                    NLS.BUNDLE.getString(NLS.KEY_COMPILER_INSTANCE_STORE_NOT_SPECIFIED)
            );
        }
    }

    /*
     *
     */

    public void compile() throws IOException {
        validate();

        // Parse template files.

        Parser parser = new Parser(syntax);

        for (Template template : templateStore.getTemplates()) {
            Unit unit = parser.parse(template);

            if (unit.hasErrors()) {
                addProblems(
                        unit.getProblems()
                );
            }
            else {
                if (unit.hasWarnings()) {
                    addProblems(
                            unit.getProblems()
                    );
                }

                units.put(
                        template.getName(), unit
                );
            }
        }

        // Generate code.

        for (Unit unit : units.values()) {
            if (isEmittable(unit)) {
                Instance instance = instanceStore.createInstance(
                        unit.getOutputPackage(),
                        unit.getOutputClassName()
                );

                Writer writer = instance.getContentWriter();
                try {
                    emitter.emit(
                            this, unit, writer
                    );
                }
                finally {
                    writer.close();
                }
            }
        }
    }

    boolean isEmittable(Unit unit) {
        return unit.getOutputClassName() != null;
    }
}
